﻿namespace Microsoft.VisualStudio.TestTools.UnitTesting
{
    using System;
    using System.Diagnostics.Contracts;
    using System.Globalization;
    using System.Runtime.Serialization;

    /// <summary>
    /// Throw when a test fails when running a test from the <see cref="TestRunner"/>.
    /// </summary>
    /// <typeparam name="TTuple">The type of the tuple.</typeparam>
    /// <remarks>
    /// Note the evil override of <see cref="Exception.StackTrace"/>. This allows us to show the message the way we
    /// want and also show the stack trace we want.
    /// </remarks>
    [Serializable]
    public class TestRunnerException<TTuple> : AssertFailedException
    {
        private readonly Exception actualException;

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="TestRunnerException&lt;TTuple&gt;"/> class.
        /// </summary>
        public TestRunnerException()
            : base()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TestRunnerException&lt;TTuple&gt;"/> class.
        /// </summary>
        /// <param name="message">The message.</param>
        public TestRunnerException(string message)
            : base(message)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TestRunnerException&lt;TTuple&gt;"/> class.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="innerException">The inner exception.</param>
        public TestRunnerException(string message, Exception innerException)
            : base(message, innerException)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TestRunnerException&lt;TTuple&gt;"/> class.
        /// </summary>
        /// <param name="tuple">The tuple.</param>
        /// <param name="itemNumber">The number of the item that failed.</param>
        /// <param name="actualException">The actual exception.</param>
        public TestRunnerException(TTuple tuple, int itemNumber, Exception actualException)
            : base(TestRunnerException<TTuple>.FormatMessage(tuple, itemNumber, actualException))
        {
            Contract.Requires(actualException != null);

            this.actualException = actualException;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TestRunnerException&lt;TTuple&gt;"/> class.
        /// </summary>
        /// <param name="info">The serialization information.</param>
        /// <param name="context">The streaming context.</param>
        protected TestRunnerException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
        #endregion Constructors

        #region Public Properties
        /// <summary>
        /// Gets a string representation of the frames on the call stack at the time the current exception was thrown.
        /// </summary>
        /// <value>A string representation of the frames on the call stack at the time the current exception was thrown.</value>
        /// <returns>
        /// A string that describes the contents of the call stack, with the most recent method call appearing first.
        /// </returns>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" PathDiscovery="*AllFiles*"/>
        /// </PermissionSet>
        /// <remarks>
        /// This is very evil, but was the best way I could figure out of getting the correct message and the correct stack
        /// trace in the test results.
        /// </remarks>
        public override string StackTrace
        {
            get
            {
                return this.actualException.StackTrace;
            }
        }
        #endregion Public Properties

        #region Public Methods
        /// <summary>
        /// Gets the object data.
        /// </summary>
        /// <param name="info">The serialization information.</param>
        /// <param name="context">The streaming context.</param>
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
        }
        #endregion Public Methods

        #region Private Methods
        private static string FormatMessage(TTuple tuple, int itemNumber, Exception actualException)
        {
            Contract.Requires(actualException != null);

            string message;
            if (actualException is AssertFailedException)
            {
                // This exception can from a failed assertion so the user doesn't need to know the exception type, etc.
                message = actualException.Message;
            }
            else
            {
                // This was not from a failed assertion so give all the information we can.
                message = actualException.ToString();
            }

            return string.Format(
                CultureInfo.InvariantCulture,
                Properties.Resources.TestFailedForCase,
                itemNumber,
                tuple,
                message);
        }
        #endregion Private Methods
    }
}